/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2002 Xodnizel
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "common.h"
#include "../../md5.h"

static int recv_tcpwrap(uint8 *buf, int len);
static void NetStatAdd(char *text);

static HWND netwin = 0;

static char *netstatt[64];
static int netstattcount = 0;
static int netlocalplayers = 1;

static char *netplayhost = 0;
static char *netplaynick = 0;
static char *netgamekey = 0;
static char *netpassword = 0;
static int remotetport = 0xFCE;

static SOCKET Socket = INVALID_SOCKET;

static int wsainit = 0;

int FCEUDnetplay = 0;
static void WSE(char *ahh) {
	char tmp[256];
	sprintf(tmp, "*** Winsock: %s", ahh);
	NetStatAdd(tmp);
}

static void en32(uint8 *buf, uint32 morp) {
	buf[0] = morp;
	buf[1] = morp >> 8;
	buf[2] = morp >> 16;
	buf[3] = morp >> 24;
}

static void FixCDis(HWND hParent, int how);
void FCEUD_NetworkClose(void) {
	NetStatAdd("*** Connection lost.");
	if (netwin) {
		SetDlgItemText(netwin, 250, "Connect");
		FixCDis(netwin, 1);
	}
	if (Socket != INVALID_SOCKET) {
		closesocket(Socket);
		Socket = INVALID_SOCKET;
	}

	if (wsainit) {
		WSACleanup();
		wsainit = 0;
	}
	/* Make sure blocking is returned to normal once network play is stopped. */
	NoWaiting &= ~2;
	FCEUDnetplay = 0;
	FCEUI_NetplayStop();
}
static void FixCDis(HWND hParent, int how) {
	int x;

	for (x = 200; x <= 206; x++)
		EnableWindow(GetDlgItem(hParent, x), how);
}
static void GetSettings(HWND hwndDlg) {
	char buf[256];
	char **strs[4] = { &netplayhost, &netplaynick, &netgamekey, &netpassword };
	int ids[4] = { 200, 203, 205, 206 };
	int x;

	for (x = 0; x < 4; x++) {
		GetDlgItemText(hwndDlg, ids[x], buf, 256);
		if (*strs[x]) {
			free(*strs[x]);
			*strs[x] = 0;
		}
		if (buf[0]) {
			*strs[x] = malloc(strlen(buf) + 1);
			strcpy(*strs[x], buf);
		}
	}
	remotetport = GetDlgItemInt(hwndDlg, 201, 0, 0);
	netlocalplayers = 1 + SendDlgItemMessage(hwndDlg, 204, CB_GETCURSEL, 0, (LPARAM)(LPSTR)0);
}

int FCEUD_NetworkConnect();

static BOOL CALLBACK NetCon(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	switch (uMsg) {
	case WM_CLOSE:
		GetSettings(hwndDlg);
		DestroyWindow(hwndDlg);
		netwin = 0;
		FCEUD_NetworkClose();
		break;
	case WM_COMMAND:
		if (HIWORD(wParam) == BN_CLICKED) {
			switch (LOWORD(wParam)) {
			case 250:
				if (FCEUDnetplay) {
					FCEUD_NetworkClose();
					SetDlgItemText(hwndDlg, 250, "Connect");
					FixCDis(hwndDlg, 1);
				}else if (GI) {
					GetSettings(hwndDlg);
					if (FCEUD_NetworkConnect()) {
						SetDlgItemText(hwndDlg, 250, "Disconnect");
						FixCDis(hwndDlg, 0);
					}
				}
				break;
			}
		}else if (HIWORD(wParam) == EN_CHANGE && Socket != INVALID_SOCKET) {
			char buf[1024];
			int t;


			t = GetDlgItemText(hwndDlg, 102, buf, 1024);
			buf[1023] = 0;

			if (strchr(buf, '\r')) {
				char *src, *dest;

				src = dest = buf;

				while (*src) {
					if (*src != '\n' && *src != '\r') {
						*dest = *src;
						dest++;
					}
					src++;
				}
				*dest = 0;
				FCEUI_NetplayText(buf);
				SetDlgItemText(hwndDlg, 102, "");
			}
		}
		break;
	case WM_INITDIALOG:
		if (netplayhost)
			SetDlgItemText(hwndDlg, 200, netplayhost);
		SetDlgItemInt(hwndDlg, 201, remotetport, 0);
		if (netplaynick)
			SetDlgItemText(hwndDlg, 203, netplaynick);
		if (netgamekey)
			SetDlgItemText(hwndDlg, 205, netgamekey);
		if (netpassword)
			SetDlgItemText(hwndDlg, 206, netpassword);

		{
			int x;
			char buf[8];
			for (x = 0; x < 4; x++) {
				sprintf(buf, "%d", x + 1);
				SendDlgItemMessage(hwndDlg, 204, CB_ADDSTRING, 0, (LPARAM)(LPSTR)buf);
			}
			SendDlgItemMessage(hwndDlg, 204, CB_SETCURSEL, netlocalplayers - 1, (LPARAM)(LPSTR)0);
		}


		break;
	}

	return 0;
}

static void NetStatAdd(char *text) {
	int x;
	uint32 totallen = 0;
	char *textbuf;

	if (!netwin) return;
	if (netstattcount >= 64) free(netstatt[netstattcount & 63]);

	if (!(netstatt[netstattcount & 63] = malloc(strlen(text) + 1)))
		return;
	strcpy(netstatt[netstattcount & 63], text);
	netstattcount++;

	if (netstattcount >= 64) {
		for (x = netstattcount & 63;; ) {
			totallen += strlen(netstatt[x]);
			x = (x + 1) & 63;
			if (x == (netstattcount & 63)) break;
			totallen += 2;
		}

		totallen++; // NULL
		textbuf = malloc(totallen);
		textbuf[0] = 0;

		for (x = netstattcount & 63;; ) {
			strcat(textbuf, netstatt[x]);
			x = (x + 1) & 63;
			if (x == (netstattcount & 63)) break;
			strcat(textbuf, "\r\n");
		}
	}else {
		for (x = 0; x < netstattcount; x++) {
			totallen += strlen(netstatt[x]);
			if (x < (netstattcount - 1))
				totallen += 2;
		}
		totallen++;
		textbuf = malloc(totallen);
		textbuf[0] = 0;
		for (x = 0; x < netstattcount; x++) {
			strcat(textbuf, netstatt[x]);
			if (x < (netstattcount - 1))
				strcat(textbuf, "\r\n");
		}
	}
	SetDlgItemText(netwin, 101, textbuf);
	free(textbuf);
	SendDlgItemMessage(netwin, 101, EM_LINESCROLL, 0, 32767);
}

void FCEUD_NetplayText(uint8 *text) {
	NetStatAdd(text);
}

int FCEUD_NetworkConnect(void) {
	WSADATA WSAData;
	SOCKADDR_IN sockin; /* I want to play with fighting robots. */
	SOCKET TSocket;
	int netdivisor;

	if (WSAStartup(MAKEWORD(1, 1), &WSAData)) {
		NetStatAdd("*** Error initializing WIndows Sockets.");
		return(0);
	}
	wsainit = 1;

	if ((TSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
		WSE("Error creating stream socket.");
		FCEUD_NetworkClose();
		return(0);
	}

	memset(&sockin, 0, sizeof(sockin));
	sockin.sin_family = AF_INET;

	{
		struct hostent *phostentb;
		unsigned long hadr;
		int sockin_len;

		sockin.sin_port = 0;
		sockin.sin_addr.s_addr = INADDR_ANY;
		sockin_len = sizeof(sockin);

		hadr = inet_addr(netplayhost);

		if (hadr != INADDR_NONE)
			sockin.sin_addr.s_addr = hadr;
		else{
			NetStatAdd("*** Looking up host name...");
			if (!(phostentb = gethostbyname((const char*)netplayhost))) {
				WSE("Error getting host network information.");
				closesocket(TSocket);
				FCEUD_NetworkClose();
				return(0);
			}
			memcpy((char*)&sockin.sin_addr, ((PHOSTENT)phostentb)->h_addr, ((PHOSTENT)phostentb)->h_length);
		}

		sockin.sin_port = htons(remotetport);
		NetStatAdd("*** Connecting to remote host...");
		if (connect(TSocket, (PSOCKADDR)&sockin, sizeof(sockin)) == SOCKET_ERROR) {
			WSE("Error connecting to remote host.");
			closesocket(TSocket);
			FCEUD_NetworkClose();
			return(0);
		}
		Socket = TSocket;
		NetStatAdd("*** Sending initialization data to server...");

		{
			uint8 *sendbuf;
			uint8 buf[1];
			uint32 sblen;

			sblen = 4 + 16 + 16 + 64 + 1 + (netplaynick ? strlen(netplaynick) : 0);
			sendbuf = malloc(sblen);
			memset(sendbuf, 0, sblen);

			en32(sendbuf, sblen - 4);

			if (netgamekey) {
				struct md5_context md5;
				uint8 md5out[16];

				md5_starts(&md5);
				md5_update(&md5, GI->MD5, 16);
				md5_update(&md5, netgamekey, strlen(netgamekey));
				md5_finish(&md5, md5out);
				memcpy(sendbuf + 4, md5out, 16);
			}else
				memcpy(sendbuf + 4, GI->MD5, 16);

			if (netpassword) {
				struct md5_context md5;
				uint8 md5out[16];

				md5_starts(&md5);
				md5_update(&md5, netpassword, strlen(netpassword));
				md5_finish(&md5, md5out);
				memcpy(sendbuf + 4 + 16, md5out, 16);
			}

			memset(sendbuf + 4 + 16 + 16, 0, 64);
			sendbuf[4 + 16 + 16 + 64] = netlocalplayers;

			if (netplaynick)
				memcpy(sendbuf + 4 + 16 + 16 + 64 + 1, netplaynick, strlen(netplaynick));

			send(Socket, sendbuf, sblen, 0);
			free(sendbuf);

			recv_tcpwrap(buf, 1);
			netdivisor = buf[0];
		}
	}


	FCEUI_NetplayStart(netlocalplayers, netdivisor);
	NetStatAdd("*** Connection established.");

	FCEUDnetplay = 1;
	int tcpopt = 1;
	if (setsockopt(TSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&tcpopt, sizeof(int)))
		puts("Nodelay fail");

	return(1);
}


int FCEUD_SendData(void *data, uint32 len) {
	send(Socket, data, len, 0);
	return(1);
}

static int recv_tcpwrap(uint8 *buf, int len) {
	fd_set fdoo;
	int t;
	struct timeval popeye;

	popeye.tv_sec = 0;
	popeye.tv_usec = 100000;

	while (len) {
		FD_ZERO(&fdoo);
		FD_SET(Socket, &fdoo);

		switch (select(0, &fdoo, 0, 0, &popeye)) {
		case 0: //BlockingCheck();
			continue;
		case SOCKET_ERROR: return(0);
		}

		t = recv(Socket, buf, len, 0);
		if (t <= 0) return(0);
		len -= t;
		buf += t;
	}
	return(1);
}

int FCEUD_RecvData(void *data, uint32 len) {
	NoWaiting &= ~2;

	for (;; ) {
		fd_set funfun;
		struct timeval popeye;

		popeye.tv_sec = 0;
		popeye.tv_usec = 100000;

		FD_ZERO(&funfun);
		FD_SET(Socket, &funfun);

		switch (select(0, &funfun, 0, 0, &popeye)) {
		case 0: continue;
		case SOCKET_ERROR: return(0);
		}

		if (FD_ISSET(Socket, &funfun)) {
			if (recv_tcpwrap(data, len) > 0) {
				unsigned long beefie;
				if (!ioctlsocket(Socket, FIONREAD, &beefie))
					if (beefie)
						NoWaiting |= 2;
				return(1);
			}else
				return(0);
		}else
			return(0);
	}
	return 0;
}

void ShowNetplayConsole(void) {
	if (!netwin)
		netwin = CreateDialog(fceu_hInstance, "NETMOO", 0, NetCon);
}

CFGSTRUCT NetplayConfig[] = {
	AC(remotetport),
	AC(netlocalplayers),
	ACS(netgamekey),
	ACS(netplayhost),
	ACS(netplaynick),
	ACS(netpassword),
	ENDCFGSTRUCT
};
